//! rsminigrep
//! 提供一个迷你的grep功能
//! 
use std::error::Error;


// 定义一个测试模块
// 如下使用 #[cfg(test)] 这是个条件编译属性，意思时当运行cargo test的时候才编译该行之下定义的模块。该模块中所有标记了 #[test] 的方法都会在 cargo test 的时候执行。
#[cfg(test)]
mod tests {
    // 使用父模块中的所有方法
    use super::*;
    #[test]
    fn test_search() {
        let query = "like";
        let content = "
In the garden of dreams, where the moonlight weaves.
A gentle breeze through the willows grieves.
Soft petals fall like whispered sighs.
Dancing in the starry skies.
The fountain's song, a liquid chime.
";
        assert_eq!(vec!["Soft petals fall like whispered sighs."], search(query, content));
    }

    #[test]
    fn test_search_insensitive() {
        // 大小写不敏感
        let query = "LIKE";
        let content = "
In the garden of dreams, where the moonlight weaves.
A gentle breeze through the willows grieves.
Soft petals fall like whispered sighs.
Dancing in the starry skies.
The fountain's song, a liquid chime.
";
        assert_eq!(vec!["Soft petals fall like whispered sighs."], search_insensitive(query, content));

    }
}


// 从main函数中抽出run函数，使得其可以独立的执行逻辑
// 执行grep的逻辑
fn _run(config: Config) {
    // 读取文件内容
    let content = std::fs::read_to_string(config.file_path).expect("could read the file");
    println!("content: {content}");
}

// 优化run方法，使得执行函数可以返回错误信息
// 返回值是Result枚举，因为没有有效结果，所以泛型T定义为空元组；错误类型想兼容各种实现了Error特征的类型，所以搞了个特征对象；
// 此处的特征对象 Box<dyn Error> 就意味着 read_to_string 后的 ? 可以将read_to_string的错误自动转换为 Box<dyn Error>
// 为什么使用Box + 特征对象呢？
// 因为特征是一个半成品，必须想办法把他标识成一个对象才能当作返回值，所以Rust用了 dyn Trait 来标识一个实现了Error的对象；
// 但是 dyn Trait 的大小在编译时时不确定的。Rust要求在编译时就知道每个变量和参数的大小，此时将其包装成为Box类型，实际上就是一个分配在堆上的指针，指向了具体的类型，这就把对象和他的大小确定了。
// 看来将read_to_string的返回值（io::Error类型) 这个实现了Error特征的对象 转为一个 Box智能指针时一件很简单的事情，? 这个宏操作，直接给转了。
// ---为什么 read_to_string 的返回值能转换为 Box<dyn Error> 呢？
// ---因为：
// ---  在 Rust 标准库中，Box<dyn Error> 实现了 From<std::io::Error> 特质。这就定义了从 std::io::Error 到 Box<dyn Error> 的转换规则。
pub fn run_e(config: Config) -> Result<(), Box<dyn Error>> {
    // ? 的作用是：有错误的话直接return；没有错误的话将结果赋值给左值。
    // ? 是一个语法糖，作用就是当结果是 Result枚举中的Err的时候，return，而且会基于返回值类型的from方法来将实际值的类型转换为返回值类型。
    // 如果我们展开 ? 的表达式的话，参考 run_e_manual 方法
    let content = std::fs::read_to_string(config.file_path)?;

    // 根据是否忽略大小写，调用不同的方法
    // rust中if是表达式，可以有值返回，直接赋值就行; 注意if和else块中的最后一行是
    let results = if config.ignore_case {
        search_insensitive(&config.query, &content)
    } else {
        search(&config.query, &content)
    };
    // 输出所有匹配到的行
    for line in results {
        println!("{line}")
    }
    Ok(())
}

// 用 match 替换 ? 
fn _run_e_manual(config: Config) -> Result<(), Box<dyn Error>> {
    let result = std::fs::read_to_string(config.file_path);
    let content = match result {
        Ok(value) => value,
        Err(err) => {
            let boxed_err: Box<dyn Error> = err.into(); // 注意此处的into方法就时from特征的反向特征Into。 当Box<dyn Error>实现了From<std::io::Error> 的时候，编译器给io::Error自动实现了的Into方法，实际上都是为了让io::Error转换为Box<dyn Error>
            return Err(boxed_err);
        }
    };
    println!("content: {content}");
    Ok(())
}

// 结构化配置信息
pub struct Config {
    pub query: String,
    pub file_path: String,
    pub ignore_case: bool,
}

// 分离命令行解析功能到该函数
// 入参是字符串数组切片类型，出参是字符串切片（其实就是字符串引用）
// 因为入参只有一个引用，所以看来编译器能够推断出他们的生命周期的依赖关系，肯定是出参的声明周期要小于入参的生命周期
fn _parse_config(args: &[String]) -> Config {
    // 注意 args 是字符串数组的引用，args[1] 直接是一个字符串，如果我们想要一个字符串切片的化，应该声明引用，即 &args[1]
    // let query = &args[1]; // 直接借用即可，反正这个参数的生命周期跨整个main
    // let file_path = &args[2];

    // 使用新创建堆内存变量的方式，脱离和环境变量的所有权关系
    let query = args[1].clone();
    let file_path = args[2].clone();
    // is_ok 用于检查Result是否是Ok，是则返回true，不是则返回false.
    let ignore_case = std::env::var("IGNORE_CASE").is_ok();
    Config{query, file_path, ignore_case} // 直接将所有权交出去了
}

// 本来想返回一个切片来着，看书上返回的是 动态数组，数组元素是 字符串引用
pub fn search<'a>(query:&'a str, content:&'a str) -> Vec<&'a str>  {
    let mut res = Vec::new();

    // 将content按照换行符分隔
    // 每一行中查询是否有query存在
    // 如果有则加如到res中
    for line in content.split("\n") {
        let pos = line.find(query);
        match pos {
            Some(_) => res.push(line),
            None => continue,
        }
    }

    res
}

// 同search一样，只不过大小写不敏感
pub fn search_insensitive<'a>(query:&'a str, content:&'a str) -> Vec<&'a str>  {
    let mut res = Vec::new();
    let binding = query.to_lowercase();
    let query_lowercase = binding.as_str();
    // 将content按照换行符分隔
    // 每一行中查询是否有query存在
    // 如果有则加如到res中
    for line in content.split("\n") {
        let pos = line.to_lowercase().find(query_lowercase);
        match pos {
            Some(_) => res.push(line),
            None => continue,
        }
    }

    res
}

// 将 parse_config 转为自己的方法，相当于使用面向对象的方式解析参数
impl Config {
    fn _new(args: &[String]) -> Config{
        if args.len() < 3 {
            panic!("not enought arguments");
        }
        let query = args[1].clone();
        let file_path = args[2].clone();
        // is_ok 用于检查Result是否是Ok，是则返回true，不是则返回false.
        let ignore_case = std::env::var("IGNORE_CASE").is_ok();
        Config{query, file_path, ignore_case}
    }

    // 修改返回参数从 Config  改为枚举 Result<Config, &str> 
    // 书中返回值的字符串切片携带生命周期，即 &'static str 暂时没明白!!!
    // 我们返回了一个字符串字面量，一般字符串字面量的生命周期是static，不过就算我没有标记，也能编译通过
    pub fn build(args: &[String]) -> Result<Config, &'static str> {
        if args.len() < 3 {
            return Result::Err("not enought arguments");
        }
        let query = args[1].clone();
        let file_path = args[2].clone();
        // is_ok 用于检查Result是否是Ok，是则返回true，不是则返回false.
        let ignore_case = std::env::var("IGNORE_CASE").is_ok();
        Result::Ok(Config{query, file_path, ignore_case})
    }
}